﻿using LucyGrid;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace LucyG
{
	/// <summary>
	/// Allows to run an existing app over the grid
	/// </summary>
	class Program
	{
		static void Main(string[] args)
		{
			new Program(args).RunGrid();
		}

		class Settings {
			public string InputDataFile = null;
			public int Instances = 0;
			public bool SharedBin = false;
			public bool Silent = false;
			public string BinPath;
		}

		Settings settings;
		private string BinPath;

		public Program(string[] args) {
			BinPath = LucyBinManager.BinPath() + "\bin";
			ParseSettings(args);
		}

		public void RunGrid() {
			if (!settings.SharedBin)
				CopyBin(Path.GetDirectoryName(settings.BinPath));
			var index = settings.BinPath.IndexOf(" ");
			var bin = settings.BinPath.Substring(0, index);
			var args = index != -1 ? settings.BinPath.Substring(index + 1) : null;
			var remoteBin = settings.SharedBin ? settings.BinPath:
				string.Format("\\Bin\\{0}", Path.GetFileName(bin));
			GetInputData()
			.Select(l => new { 
				Input = l,
				Bin = remoteBin,
				Args = args
			})
			.AsParallelGrid(l =>
			{
				var res = string.Empty;
				try
				{
					var p = Process.Start(new ProcessStartInfo()
					{
						RedirectStandardInput = true,
						RedirectStandardOutput = true,
						UseShellExecute = false,
						FileName = l.Bin,
						Arguments = l.Args
					});
					if (!string.IsNullOrEmpty(l.Input))
						p.StandardInput.WriteLine(l.Input);
					p.WaitForExit();
					res = p.StandardOutput.ReadToEnd();
				}
				catch (Exception ex) {
					res = ex.ToString();
				}
				return new
				{
					Result = res
				};
			})
			.All(l => {
				Console.WriteLine(l.Result);
				return true;
			});
		}

		/// <summary>
		/// Returns input data
		/// </summary>
		/// <returns></returns>
		public IEnumerable<string> GetInputData() {
			string line = null;
			if (settings.Silent)
			{
				foreach (var l in Enumerable.Range(0, settings.Instances))
					yield return string.Empty;
			}
			else if (settings.Instances != 0)
			{
				System.Console.Write("Enter data: ");
				line = System.Console.ReadLine();
				foreach (var l in Enumerable.Range(0, settings.Instances).Select(i => line))
					yield return l;
			}
			else
			{
				using (var s = File.OpenRead(settings.InputDataFile))
				{
					using (var sr = new StreamReader(s))
					{
						while ((line = sr.ReadLine()) != null)
							yield return line;
					}
				}
			}
		}

		/// <summary>
		/// Get settings
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		void ParseSettings(string[] args)
		{
			settings = new Settings();
			var index = -1;
			if ((index = Array.IndexOf(args, "-f")) != -1)
				settings.InputDataFile = args[index + 1];
			if ((index = Array.IndexOf(args, "-i")) != -1)
				settings.Instances = int.Parse(args[index + 1]);
			if ((index = Array.IndexOf(args, "-nc")) != -1)
				settings.SharedBin = true;
			if ((index = Array.IndexOf(args, "-s")) != -1)
			{
				settings.Silent = true;
				settings.Instances = int.Parse(args[index + 1]);
			}
			settings.BinPath = args.Last();
		}

		/// <summary>
		/// 
		/// </summary>
		void ShowHelp() {
			new string[]{
				"LuvyG.exe USAGE",
				"LuvyG.exe [OPTIONS] binPath",
				string.Empty,
				"OPTIONS",
				"-f filename     Input data file. File lines will be used as input chunks",
				"                to pass to the standart input of the application",
				"-i number       Interactive mode. The programm will create 'number'",
				"-s number       Silent mode, no input data only instances running the command.",
				"                instances and read a line from the standar input and ",
				"                and use it as the input data.",
				"-nc             Indicates to do not copy the binary across the gridnetwork.",
				"                With this option 'binPtah' should point to a network path",
				"                or local path to work",
				string.Empty,
				"binPath         Executable path to run on the grid"
			}.All(l => 
				{
					System.Console.WriteLine(l);
					return true;
				}
			);
		}
		/// <summary>
		/// Copies binary to bin directory to be share acrross the network
		/// </summary>
		/// <param name="path"></param>
		void CopyBin(string path) {
			if (Directory.Exists(BinPath))
				Directory.Delete(BinPath);
			DirectoryCopy(path, BinPath, true);
		}

		/// <summary>
		/// Sure?? There's no Directory.Copy ??
		/// http://msdn.microsoft.com/en-us/library/bb762914(v=vs.110).aspx
		/// </summary>
		/// <param name="sourceDirName"></param>
		/// <param name="destDirName"></param>
		/// <param name="copySubDirs"></param>
		private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
		{
			// Get the subdirectories for the specified directory.
			DirectoryInfo dir = new DirectoryInfo(sourceDirName);
			DirectoryInfo[] dirs = dir.GetDirectories();

			if (!dir.Exists)
			{
				throw new DirectoryNotFoundException(
					"Source directory does not exist or could not be found: "
					+ sourceDirName);
			}

			// If the destination directory doesn't exist, create it. 
			if (!Directory.Exists(destDirName))
			{
				Directory.CreateDirectory(destDirName);
			}

			// Get the files in the directory and copy them to the new location.
			FileInfo[] files = dir.GetFiles();
			foreach (FileInfo file in files)
			{
				string temppath = Path.Combine(destDirName, file.Name);
				file.CopyTo(temppath, false);
			}

			// If copying subdirectories, copy them and their contents to new location. 
			if (copySubDirs)
			{
				foreach (DirectoryInfo subdir in dirs)
				{
					string temppath = Path.Combine(destDirName, subdir.Name);
					DirectoryCopy(subdir.FullName, temppath, copySubDirs);
				}
			}
		}
	}
}
